package system.service.sys.impl;

import com.github.pagehelper.PageHelper;
import com.github.pagehelper.PageInfo;
import framework.config.SecurityConfig;
import framework.config.UserConfig;
import framework.defined.SystemDefined;
import framework.exceptions.BusinessException;
import framework.security.AuthService;
import framework.security.RegApproval;
import framework.security.password.PasswordService;
import framework.utils.DateUtil;
import framework.utils.ErrorUtil;
import framework.utils.RequestUtil;
import lombok.Getter;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.RandomStringUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.Cache;
import org.springframework.cache.CacheManager;
import org.springframework.cache.annotation.CacheConfig;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.dao.DuplicateKeyException;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import system.defined.UserEventType;
import system.dto.UserSenseQuery;
import system.dto.sys.SysUserQuery;
import system.entity.*;
import system.mapper.SysUserMapper;
import system.service.sys.*;

import java.time.ZoneOffset;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.stream.Collectors;

@Slf4j
@Service
@CacheConfig(cacheNames = "global")
public class SysUserServiceImpl implements SysUserService {
    /**
     * 密码盐长度
     */
    public static final int SALT_LENGTH = 19;

    @Autowired
    @Getter
    private SysUserMapper mapper;
    @Autowired
    private CacheManager cacheManager;
    @Autowired
    private SysUserEventService eventService;
    @Autowired
    private SysPermissionService permissionService;
    @Autowired
    private SecurityConfig securityConfig;
    @Autowired
    private SysLoginService sysLoginService;
    @Autowired
    private SysRoleUserService sysRoleUserService;
    @Autowired
    private SysLoginFailCounterService loginFailCounterService;
    @Autowired
    private SysLoginFailLimitService loginFailLimitService;
    @Autowired
    private UserConfig userConfig;
    @Autowired
    private AuthService authService;
    @Autowired
    private PasswordService passwordService;
    @Autowired
    private SysConfigService configService;

    @Override
    @SneakyThrows
    @Transactional
    public Integer add(SysUser user) {
        Date now = new Date();

        if (StringUtils.isEmpty(user.getUsername())) {
            throw new BusinessException(RequestUtil
                    .getMessageDefault("user.add.noInputUserName", "Please input username"));
        }

        if (StringUtils.isEmpty(user.getPassword())) {
            throw new BusinessException(RequestUtil
                    .getMessageDefault("user.add.noInputPassword", "Please input password"));
        }

        if (StringUtils.isBlank(user.getMail())) {
            user.setMail(null);
        }

        if (StringUtils.isBlank(user.getMobile())) {
            user.setMobile(null);
        }

        //密码
        String pwd = user.getPassword();
        String salt = RandomStringUtils.randomGraph(SALT_LENGTH);
        String encodePwd = this.passwordService.encode(pwd, salt);

        //
        user.setPassword(encodePwd);
        user.setPasswordSalt(salt);
        user.setPasswordUpdate(now);

        if (user.getPasswordExpired() == null) {
            if (userConfig.getPasswordDefaultExpireDay() > 0)
                user.setPasswordExpired(new Date(now.getTime() + (userConfig.getPasswordDefaultExpireDay() * 24 * 3600)));
            else
                user.setPasswordExpired(Date.from(DateUtil.MAX_LOCAL_TIME.toInstant(ZoneOffset.UTC)));
        }

        //
        user.setCreateTime(now);
        user.setUpdateTime(now);
        user.setRegApproval(RegApproval.Agree.ordinal());

        //
        if (user.getExpiredTime() == null) {
            user.setExpiredTime(Date.from(DateUtil.MAX_LOCAL_TIME.toInstant(ZoneOffset.UTC)));
        }

        //
        Integer add = null;
        try {
            add = this.mapper.insert(user);
        } catch (DuplicateKeyException exception) {
            throw new BusinessException(RequestUtil
                    .getMessageDefault("user.add.usernameHaveExists", "Username have exists"));
        }

        //添加默认角色
        if (userConfig.getDefaultRoleId() > 0) {
            SysRoleUser roleUser = new SysRoleUser();
            roleUser.setUserId(user.getId());
            roleUser.setRoleId(userConfig.getDefaultRoleId());
            sysRoleUserService.add(roleUser);
        }

        //用户事件: 账户添加
        SysUserEvent event = new SysUserEvent();
        event.setUserId(user.getId());
        event.setOperateUserId(authService.getAccountId());
        event.setCreateTime(now);
        event.setEventType(UserEventType.Create);
        this.eventService.add(event);

        //
        return add;
    }

    @Override
    public Integer update(SysUser user) {
        Date now = new Date();
        user.setCreateTime(null);
        user.setUpdateTime(now);
        //禁止状态变更
        user.setIsDisabled(null);
        //不允许通过编辑修改密码，应使用单独的密码设置功能
        user.setPassword(null);
        user.setPasswordUpdate(null);
        user.setPasswordExpired(null);
        user.setPasswordSalt(null);
        int rows = SysUserService.super.update(user);
        //缓存删除
        this.cacheUserDelete(user.getId());
        //
        return rows;
    }

    @Override
    public Integer updateProfile(SysUser user) {
        Date now = new Date();
        user.setUpdateTime(now);
        //禁止修改项
        user.setUsername(null);
        //
        if (!StringUtils.isBlank(user.getMail())) {
            user.setIsMail(SystemDefined.NO_VALUE);
        } else {
            user.setIsMail(null);
            user.setMail(null);
        }
        //
        if (!StringUtils.isBlank(user.getMobile())) {
            user.setIsMobile(SystemDefined.NO_VALUE);
        } else {
            user.setIsMobile(null);
            user.setMobile(null);
        }
        //
        int rows = mapper.updateProfile(user);

        //用户事件: 账户添加
        SysUserEvent event = new SysUserEvent();
        event.setUserId(user.getId());
        event.setOperateUserId(user.getId());
        event.setCreateTime(now);
        event.setEventType(UserEventType.ProfileModify);
        rows += this.eventService.add(event);

        //缓存删除
        this.cacheUserDelete(user.getId());
        //
        return rows;
    }

    /**
     * 启禁用
     *
     * @param id
     * @param isDisabled
     * @param cause
     * @return
     */
    @Override
    @Transactional
    public Integer disable(Long id, boolean isDisabled, String cause) {
        int rows = 0;
        Date now = new Date();

        if (userConfig.checkIsSuperAdminId(id)) {
            ErrorUtil.error("error.user.not.operate", "this operation is cannot to target user");
            return rows;
        }

        //账号
        SysUser user = new SysUser();
        user.setId(id);
        user.setIsDisabled(isDisabled ? SystemDefined.YES_VALUE : SystemDefined.NO_VALUE);
        user.setUpdateTime(now);

        //用户事件
        SysUserEvent event = new SysUserEvent();
        event.setUserId(user.getId());
        event.setOperateUserId(authService.getAccountId());
        event.setCreateTime(now);
        if (isDisabled)
            event.setEventType(UserEventType.Disabled);
        else
            event.setEventType(UserEventType.Enabled);
        event.setCause(cause);
        rows += this.eventService.add(event);
        rows += SysUserService.super.update(user);

        //缓存删除
        this.cacheUserDelete(user.getId());

        //
        return rows;
    }

    /**
     * 获取用户权限列表
     *
     * @param id
     * @return
     */
    private List<String> loadUserRolePermissions(Long id) {
        List<String> list = this.mapper.loadUserRolePermissions(id);
        //添加默认的AUTH权限
        list.add("AUTH");
        return list;
    }

    /**
     * 获取用户全部权限列表
     *
     * @param id
     * @return
     */
    @Override
    public List<String> loadUserPermissions(Long id) {
        List<String> permissions = new ArrayList<>();

        //缓存命中
        String cacheKey = "lup_" + this.configService.getConfigVersion() + "_" + id;
        Cache cache = this.cacheManager.getCache("global");
        permissions = cache.get(cacheKey, permissions.getClass());
        if (permissions != null) {
            return permissions;
        }

        //管理员则直接返回全部权限
        if (this.userConfig.getSuperAdminId() != null
                && this.userConfig.getSuperAdminId() != 0L
                && this.userConfig.getSuperAdminId().equals(id)
        ) {
            //load all permission
            log.warn("admin id " + id + " load all permissions");
            //若是超级管理员，默认具有全部权限
            SysPermission permission = new SysPermission();
            permission.setIsDisabled(SystemDefined.NO_VALUE);
            List<SysPermission> list = this.permissionService.load(permission);
            permissions = list.stream().map(p -> p.getId()).collect(Collectors.toList());
        } else {

            //获取角色权限
            permissions = this.loadUserRolePermissions(id);

            //获取岗位权限
            // List<String> postPermissions = this.sysUserMapper.loadUserPostPermissions(accountId);
            //permissions.addAll(postPermissions);
        }

        //
        cache.put(cacheKey, permissions);
        return permissions;
    }

    @Override
    @Transactional
    public int loginSuccessful(String username, Long id, String ip, String os, String browser) {
        int rows = 0;
        Date now = new Date();
        //更新登录计数
        rows += mapper.updateLogin(username, now, ip);

        //添加日记录
        SysLogin login = new SysLogin();
        login.setUsername(username);
        login.setUserId(id);
        login.setOs(os);
        login.setCreateTime(now);
        login.setIpaddr(ip);
        login.setBrowser(browser);
        login.setStatus(SystemDefined.SUCCESS_VALUE);
        rows += sysLoginService.add(login);

        //删除失败计数
        if (this.userConfig.getLoginFailMaxLimit() > 0) {
            SysLoginFailCounter counter = new SysLoginFailCounter();
            counter.setUsername(username);
            rows += loginFailCounterService.delete(counter, item -> item.getId());
        }

        //
        return rows;
    }

    @Override
    @Transactional
    public Integer deleteById(Long id) {
        int rows = 0;
        //delete user
        rows += SysUserService.super.deleteById(id);
        //delete user role
        SysRoleUser roleUser = new SysRoleUser();
        roleUser.setUserId(id);
        rows += sysRoleUserService.delete(roleUser, item -> item.getId());
        //缓存删除
        this.cacheUserDelete(id);
        //
        return rows;
    }

    @Override
    @Transactional
    public Integer deleteByIds(List<Long> ids) {
        int rows = 0;
        for (Long id : ids) {
            rows += this.deleteById(id);
        }
        return rows;
    }

    @Override
    @Transactional
    public int loginUnsuccessful(String username, String ip, String os, String browser, String cause) {
        int rows = 0;
        Date now = new Date();
        //更新登录计数
        rows += mapper.updateLogin(username, now, ip);

        //用户加载
        SysUser userQuery = new SysUser();
        userQuery.setUsername(username);
        SysUser user = getMapper().one(userQuery);

        //添加登录记录
        //仅添加已经注册的用户登录记录
        if (user != null) {
            SysLogin login = new SysLogin();
            login.setUsername(username);
            login.setUserId(user.getId());
            login.setOs(os);
            login.setCreateTime(now);
            login.setIpaddr(ip);
            login.setBrowser(StringUtils.abbreviate(browser, 240));
            login.setStatus(SystemDefined.FAILURE_VALUE);
            login.setRemark(StringUtils.abbreviate(cause, 240));
            rows += sysLoginService.add(login);
        }

        //添加失败计数
        if (this.userConfig.getLoginFailMaxLimit() > 0) {
            SysLoginFailCounter counter = new SysLoginFailCounter();
            counter.setCreateTime(now);
            counter.setUsername(username);
            loginFailCounterService.add(counter);
            //check
            Date expired = new Date(now.getTime() - userConfig.getLoginFailCounterExpireSeconds() * 1000);
            int noExpiredSize = loginFailCounterService.listNoExpiredSize(username, expired);
            if (noExpiredSize > userConfig.getLoginFailMaxLimit()) {
                //添加登录限制记录
                SysLoginFailLimit limit = new SysLoginFailLimit();
                limit.setUsername(username);
                limit.setCreateTime(now);
                limit.setExpireTime(new Date(now.getTime() + userConfig.getLoginFailCounterExpireSeconds() * 1000));
                try {
                    loginFailLimitService.add(limit);
                } catch (DuplicateKeyException exception) {
                    //忽略重复添加
                }
            }
        }

        //
        return rows;
    }

    @Override
    public void logoutSuccessful(Long id) {
        this.cacheUserDelete(id);
    }

    @Override
    @Cacheable(key = "'name_'+#username")
    public SysUser loadByUsername(String username) {
        SysUser query = new SysUser();
        query.setUsername(username);
        SysUser user = mapper.one(query);
        return user;
    }

    @Override
    @Cacheable(key = "'id_'+#id")
    public SysUser loadById(Long id) {
        SysUser user = SysUserService.super.loadById(id);
        return user;
    }

    @Override
    public PageInfo<SysUser> search(SysUserQuery query, Integer pageIndex, Integer pageSize) {
        PageHelper.startPage(pageIndex, pageSize);
        List<SysUser> list = mapper.search(query);
        return PageInfo.of(list);
    }

    @Override
    public List<SysUser> sense(UserSenseQuery senseQuery, Integer pageIndex, Integer pageSize) {
        PageHelper.startPage(pageIndex, pageSize);
        List<SysUser> list = mapper.sense(senseQuery);
        return list;
    }

    @Override
    @Transactional
    public int updatePassword(Long id, String oldPassword, String newPassword) {
        int rows = 0;
        SysUser user = this.loadById(id);
        if (user == null) return rows;

        //
        if (userConfig.checkIsSuperAdminId(id) && SystemDefined.YES_VALUE.equals(userConfig.getSuperAdminPasswordChange())) {
            ErrorUtil.error("user.passwordChangeDisable", "No allow modify password for the user");
            return rows;
        }

        //
        boolean matched = this.passwordService.matched(oldPassword, user.getPasswordSalt(), user.getPassword());
        if (!matched) {
            ErrorUtil.error("user.password.old.error", "Old password error");
            return rows;
        }

        //
        Date now = new Date();

        //密码
        String salt = RandomStringUtils.randomGraph(SALT_LENGTH);
        String encodePwd = this.passwordService.encode(newPassword, salt);

        //
        SysUser update = new SysUser();
        update.setId(id);
        update.setPassword(encodePwd);
        update.setPasswordSalt(salt);
        update.setPasswordUpdate(now);
        update.setPasswordNext(SystemDefined.NO_VALUE);

        if (userConfig.getPasswordDefaultExpireDay() > 0)
            update.setPasswordExpired(new Date(now.getTime() + (userConfig.getPasswordDefaultExpireDay() * 24 * 3600)));
        else
            update.setPasswordExpired(Date.from(DateUtil.MAX_LOCAL_TIME.toInstant(ZoneOffset.UTC)));

        //用户事件
        SysUserEvent event = new SysUserEvent();
        event.setUserId(id);
        event.setOperateUserId(authService.getAccountId());
        event.setCreateTime(now);
        event.setEventType(UserEventType.PasswordModify);
        rows += eventService.add(event);

        //
        rows += mapper.update(update);

        //缓存删除
        this.cacheUserDelete(id);

        //
        return rows;
    }

    @Override
    @Transactional
    public int setPassword(Long id, String password, boolean nextMustModify) {
        int rows = 0;
        Date now = new Date();

        //
        if (userConfig.checkIsSuperAdminId(id) && SystemDefined.YES_VALUE.equals(userConfig.getSuperAdminPasswordChange())) {
            ErrorUtil.error("user.passwordChangeDisable", "No allow modify password for the user");
            return rows;
        }

        //密码
        String salt = RandomStringUtils.randomGraph(SALT_LENGTH);
        String encodePwd = this.passwordService.encode(password, salt);

        //
        SysUser update = new SysUser();
        update.setId(id);
        update.setPassword(encodePwd);
        update.setPasswordSalt(salt);
        update.setPasswordUpdate(now);
        if (nextMustModify) {
            update.setPasswordNext(SystemDefined.YES_VALUE);
        } else {
            update.setPasswordNext(SystemDefined.NO_VALUE);
        }

        //
        if (userConfig.getPasswordDefaultExpireDay() > 0)
            update.setPasswordExpired(new Date(now.getTime() + (userConfig.getPasswordDefaultExpireDay() * 24 * 3600)));
        else
            update.setPasswordExpired(Date.from(DateUtil.MAX_LOCAL_TIME.toInstant(ZoneOffset.UTC)));

        //用户重置
        SysUserEvent event = new SysUserEvent();
        event.setUserId(id);
        event.setOperateUserId(authService.getAccountId());
        event.setCreateTime(now);
        event.setEventType(UserEventType.PasswordReset);
        rows += eventService.add(event);

        //
        rows += mapper.update(update);

        //缓存删除
        this.cacheUserDelete(id);

        //
        return rows;
    }

    @Override
    @Transactional
    public Integer approve(Long id, RegApproval approval, String msg) {
        Date now = new Date();
        //
        SysUser user = mapper.loadById(id);
        if (user == null)
            throw new BusinessException(RequestUtil.getMessageDefault("user.operate.not.user", "Not found user"));

        if (!user.getRegApproval().equals(RegApproval.Waiting.ordinal()))
            return 0;

        //
        SysUser update = new SysUser();
        update.setId(id);
        update.setRegApproval(approval.ordinal());

        //用户事件
        SysUserEvent event = new SysUserEvent();
        event.setUserId(id);
        event.setOperateUserId(authService.getAccountId());
        event.setCreateTime(now);
        event.setCause(msg);
        event.setEventType(UserEventType.RegisterApproval);

        //
        int rows = mapper.update(update);
        rows += eventService.add(event);

        //缓存删除
        this.cacheUserDelete(id);

        //
        return rows;
    }

    /**
     * 删除缓存
     *
     * @param id
     */
    public void cacheUserDelete(Long id) {
        Cache uc = cacheManager.getCache("global");
        uc.evict("id_" + id);
        uc.evict("lup_" + this.configService.getConfigVersion() + "_" + id);
        //
        SysUser user = mapper.loadById(id);
        if (user != null) {
            uc.evict("name_" + user.getUsername());
        }
    }
}